#include "stdlib.hpp"
#include "libfile.hpp"
#include <purewell/mutex.hpp>

namespace purewell
{

#ifdef __PW_HAVE_POSIX_MUTEX
typedef struct __pw_mutex_t
{
	pthread_mutex_t		lock;
	bool				pshared;
	int					fd;

	inline __pw_mutex_t() : pshared(false), fd(-1) {}
} __pw_mutex_t;

#	define _xchg() &(((__pw_mutex_t*)(m_pData))->lock)
#endif//__PW_HAVE_POSIX_MUTEX

#ifdef __PW_HAVE_POSIX_MUTEX

inline
static
void
__PWDestroyMutex(__pw_mutex_t* dat)
{
	pthread_mutex_destroy(&(dat->lock));
	if ( dat->pshared )
	{
		__pw_shinst_t si;
		si.p = dat;
		si.fd = dat->fd;
		si.size = sizeof(__pw_mutex_t);
		__PWDestroySharedInstance(si);
	}
	else
	{
		delete dat;
	}
}

inline
static
__pw_mutex_t*
__PWCreateMutexShared(const pthread_mutexattr_t* attr)
{
	__pw_mutex_t* dat(NULL);
	__pw_shinst_t si;

	do
	{
		if ( !__PWCreateSharedInstance(si, sizeof(__pw_mutex_t)) )
		{
			break;
		}

		dat = (__pw_mutex_t*)si.p;

		if ( 0 != pthread_mutex_init(&(dat->lock), attr) )
		{
			dat = NULL;
			break;
		}

		dat->pshared = true;
		dat->fd = si.fd;

		return dat;
	} while (false);

	if ( si.p )
	{
		__PWDestroySharedInstance(si);
	}

	return NULL;
}

inline
static
__pw_mutex_t*
__PWCreateMutexPrivate(const pthread_mutexattr_t* attr)
{
	__pw_mutex_t* dat(NULL);

	do
	{
		dat = new __pw_mutex_t;
		if ( NULL == dat ) break;

		if ( 0 != pthread_mutex_init(&(dat->lock), attr) )
		{
			break;
		}

		return dat;
	} while (false);

	if ( dat )
	{
		delete dat;
		dat = NULL;
	}

	return NULL;
}

#endif//__PW_HAVE_POSIX_MUTEX

PWMutex::PWMutex()
{
}

PWMutex::~PWMutex()
{
#ifdef __PW_HAVE_POSIX_MUTEX
	if ( NULL == m_pData ) return;
	__PWDestroyMutex((__pw_mutex_t*)m_pData);
	m_pData = NULL;
#endif//__PW_HAVE_POSIX_MUTEX
}

void
PWMutex::release(PWMutex* lock)
{
	delete lock;
}

PWMutex*
PWMutex::create(int type, bool pshared)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	PWMutexAttribute* pattr(PWMutexAttribute::create());
	if ( NULL == pattr ) return NULL;

	PWMutex* cls(NULL);

	do
	{
		if ( !pattr->setType(type) ) break;
		if ( !pattr->setShared(pshared) ) break;

		cls = create(pattr);

	} while (false);

	PWMutexAttribute::release(pattr);

	return cls;
#else
	__PWERRNO_NOIMPL();
	return NULL;
#endif//__PW_HAVE_POSIX_MUTEX
}

PWMutex*
PWMutex::create(const PWMutexAttribute* pattr)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	bool pshared(false);
	const pthread_mutexattr_t* nattr(NULL);
	if ( pattr )
	{
		if ( !pattr->getShared(pshared) ) return NULL;
		nattr = (const pthread_mutexattr_t*)(pattr->getData());
	}

	PWMutex* cls(new PWMutex);
	if ( NULL == cls ) return NULL;

	__pw_mutex_t* dat(NULL);

	do
	{
		dat = pshared ? __PWCreateMutexShared(nattr)
			: __PWCreateMutexPrivate(nattr);
		if ( NULL == dat ) break;

		cls->m_pData = dat;
		return cls;
	} while (false);

	if ( cls )
	{
		delete cls;
		cls = NULL;
	}

	return NULL;
#else
	__PWERRNO_NOIMPL();
	return NULL;
#endif//__PW_HAVE_POSIX_MUTEX
}

void
PWMutex::lock(void)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	pthread_mutex_lock(_xchg());
#else
	__PWERRNO_NOIMPL();
#endif
}

void
PWMutex::unlock(void)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	pthread_mutex_unlock(_xchg());
#else
	__PWERRNO_NOIMPL();
#endif
}

bool
PWMutex::lockTest(void)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	return (0 == pthread_mutex_trylock(_xchg()));
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWMutex::lockTimeLimit(size_t msec)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	struct timespec ts;
	ts.tv_sec = msec / 1000;
	ts.tv_nsec = (msec % 1000) * 1000;
	return 0 == pthread_mutex_timedlock(_xchg(), &ts);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

PWMutexAttribute::PWMutexAttribute()
{
}

PWMutexAttribute::~PWMutexAttribute()
{
#ifdef __PW_HAVE_POSIX_MUTEX
	if ( !m_pData ) return;

	pthread_mutexattr_t* pthis((pthread_mutexattr_t*)m_pData);
	pthread_mutexattr_destroy(pthis);
	delete pthis;
	m_pData = NULL;
#endif
}

PWMutexAttribute*
PWMutexAttribute::create(void)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	PWMutexAttribute* cls(new PWMutexAttribute);
	if ( NULL == cls ) return NULL;

	pthread_mutexattr_t* dat(NULL);

	do
	{
		if ( NULL == (dat = new pthread_mutexattr_t) ) break;
		if ( 0 != pthread_mutexattr_init(dat) ) break;

		cls->m_pData = dat;
		return cls;
	} while (false);

	if ( cls )
	{
		delete cls;
	}

	return NULL;
#else
	__PWERRNO_NOIMPL();
	return NULL;
#endif
}

void
PWMutexAttribute::release(PWMutexAttribute* obj)
{
	delete obj;
}

bool
PWMutexAttribute::setType(int type)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	pthread_mutexattr_t* pthis((pthread_mutexattr_t*)m_pData);
	return 0 == pthread_mutexattr_settype(pthis, type);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWMutexAttribute::getType(int& type) const
{
#ifdef __PW_HAVE_POSIX_MUTEX
	pthread_mutexattr_t* pthis((pthread_mutexattr_t*)m_pData);
	return 0 == pthread_mutexattr_gettype(pthis, &type);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWMutexAttribute::setShared(bool pshared)
{
#ifdef __PW_HAVE_POSIX_MUTEX
	pthread_mutexattr_t* pthis((pthread_mutexattr_t*)m_pData);
	return 0 == pthread_mutexattr_setpshared(pthis,
		pshared?PTHREAD_PROCESS_SHARED:PTHREAD_PROCESS_PRIVATE);
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

bool
PWMutexAttribute::getShared(bool& pshared) const
{
#ifdef __PW_HAVE_POSIX_MUTEX
	const pthread_mutexattr_t* pthis((const pthread_mutexattr_t*)m_pData);

	int type;
	bool res( 0 == pthread_mutexattr_getpshared(pthis, &type) );

	if ( res )
	{
		pshared = (type == PTHREAD_PROCESS_SHARED);
	}

	return res;
#else
	__PWERRNO_NOIMPL();
	return false;
#endif
}

};//namespace purewell

